/*
 * Author: nicolas.bredeche(@)lri.fr
 * Created on 21 dec. 2006
 * 
 * the NeuralNetwork class provide a network framework that differs from a NeuralNetworkWithBias only by the fact that it does not include any bias neurons/arcs.
 * This class is useful for starting creating topologies with information transfer (ex.: Braitenberg nets, Petri nets, Bayesian nets, etc.)
 * The only method to write is the step() method.
 * 
 */

package piconode.core.node;

import java.util.ArrayList;

import piconode.core.arc.ConstrainedArc;
import piconode.core.arc.ModularArc;
import piconode.core.arc.WeightedArc;
import piconode.toolbox.Display;
import piconode.visualpiconode.XmlRepresentation;

/**
 * The NeuralNetwork class provides the minimal basis to define a neural network
 * module.
 */
public class NeuralNetwork extends Network implements XmlRepresentation {

	/*
	 * Data
	 */

	protected ArrayList _outputNeuronsList = new ArrayList();
	protected ArrayList _inputNeuronsList = new ArrayList();

	protected boolean _init = false; // used to check if network is
	// initialised before using it.
	protected ArrayList _allNeuronsUnorderedList = new ArrayList();

	/*
	 * Constructors
	 */

	/**
	 * create a new network with no bias neuron.
	 */
	public NeuralNetwork() // by default, network does not contain a biased
	// entry linked to every neurons (except entry node)
	{
	}

	/*
	 * Methods
	 */

	protected int getInputNeuronListSize() {
		return (_inputNeuronsList.size());
	}

	/*
	 * public int getNodeListSize () { return ( _nodeList.size() ); }
	 * 
	 * public ConnectedNode getNodeAt (int __index) { return (
	 * (Neuron)_nodeList.get(__index) ); }
	 */

	/**
	 * return the totat number of arcs (same as getArcListSize())
	 * 
	 * @return
	 */
	public int getNumberOfAllArcs() {
		return getArcListSize();
	}

	/**
	 * return a list with weights values from all arcs (same as
	 * getWeightsFromStdArcs())
	 * 
	 * @return
	 */
	public ArrayList getWeightsFromAllArcs() {
		return this.getWeightsFromStdArcs();
	}

	/**
	 * Calls setStdArcsWrightValues (note: no bias node)
	 * 
	 * @param __list
	 */
	public void setAllArcsWeightValues(ArrayList __list) {
		if (__list.size() != getNumberOfAllArcs()) {
			System.out.println("NeuralNetwork::setAllArcsWeightValues(-) - number of values and weights do not match (waiting for " + getNumberOfAllArcs() + ", " + __list.size() + " given.)");
			System.exit(-1);
		}

		int index = 0;

		// load the weights for the standard arcs

		ArrayList stdWeightList = new ArrayList();

		for (; index != this.getArcListSize(); index++)
			stdWeightList.add(new Double(((Double) __list.get(index)).doubleValue()));

		this.setStdArcsWeightValues(stdWeightList);

	}

	/**
	 * return the number of output values
	 */
	public int getOutputNeuronListSize() {
		return (_outputNeuronsList.size());
	}

	/**
	 * return a list that contains all output neurons values
	 */
	public double[] getOutputNeuronValues() {
		double[] values = new double[_outputNeuronsList.size()];
		for (int i = 0; i != _outputNeuronsList.size(); i++)
			values[i] = this.getOutputNeuronAt(i).getValue();
		return (values);
	}

	/**
	 * return a specific output neuron
	 * 
	 * @param __index
	 * @return
	 */
	public NeuronalNode getOutputNeuronAt(int __index) {
		return ((NeuronalNode) _outputNeuronsList.get(__index));
	}

	/**
	 * return a specific output neuron with the name given as parameter slower
	 * than corresponding indexed-based get method but useful for debugging.
	 * 
	 * @param __neuron
	 */
	public NeuronalNode getOutputNeuron(String __name) {
		for (int i = 0; i != _outputNeuronsList.size(); i++)
			if (((NeuronalNode) _outputNeuronsList.get(i)).getName() == __name)
				return (NeuronalNode) _outputNeuronsList.get(i);
		System.out.println("NeuronalNode.getOutputNeuron(.) - neuron \"" + __name + "\"not found.");
		System.exit(-1);
		return null;
	}

	/**
	 * specify an input neuron.
	 */
	public void registerInputNeuron(NeuronalNode __neuron) {
		__neuron.setNetworkInputFlag(true);
		_inputNeuronsList.add(__neuron);
	}

	/**
	 * specify an output neuron.
	 * 
	 * @param __neuron
	 */
	public void registerOutputNeuron(NeuronalNode __neuron) {
		__neuron.setNetworkOutputFlag(true);
		_outputNeuronsList.add(__neuron);
	}

	/**
	 * return a specific input neuron with the name given as parameter slower
	 * than corresponding indexed-based get method but useful for debugging.
	 * 
	 * @param __neuron
	 */
	public NeuronalNode getInputNeuron(String __name) {
		for (int i = 0; i != _inputNeuronsList.size(); i++)
			if (((NeuronalNode) _inputNeuronsList.get(i)).getName() == __name)
				return (NeuronalNode) _inputNeuronsList.get(i);
		System.out.println("NeuronalNode.getInputNeuron(.) - neuron \"" + __name + "\"not found.");
		System.exit(-1);
		return null;
	}

	/**
	 * register a "standard" arc into the network. in the special case of a
	 * neural network - arcs from the bias neuron are automaticaly registered
	 * Thus, the user should not explicitly register "bias" arc.
	 * 
	 * @param __arc
	 */
	@Override
	public void registerArc(ConstrainedArc __arc) {
		_arcList.add(__arc);
	}

	/**
	 * set values for arcs - useful for evolution (update arcs values through
	 * evolutionary process)
	 * 
	 * @param __valuesList
	 */

	public void setStdArcsWeightValues(ArrayList __valuesList) {
		if (__valuesList.size() != getArcListSize()) {
			System.out.println("NeuralNetwork::setStdArcsWeightValues(-) - number of values and weights do not match (" + __valuesList.size() + " given, waiting for " + getArcListSize() + ")");
			System.exit(-1);
		}

		for (int i = 0; i != __valuesList.size(); i++) {
			((WeightedArc) _arcList.get(i)).setWeightValue(((Double) __valuesList.get(i)).doubleValue());
		}
	}

	/**
	 * return only values for arcs *excepting* those from the biased neuron -
	 * useful for evolution (update arcs values through evolutionary process)
	 * 
	 * @return
	 */
	public ArrayList getWeightsFromStdArcs() {
		ArrayList valuesList = new ArrayList();

		for (int i = 0; i != this.getArcListSize(); i++) {
			valuesList.add(new Double(((WeightedArc) this.getArcAt(i)).getWeightValue()));
		}

		return (valuesList);
	}

	/**
	 * display information on the neural network (structure, weight, learnable
	 * nodes, input and output values)
	 * 
	 */
	@Override
	public void displayInformation() {
		System.out.println("### Network Information : summary ###");
		for (int i = 0; i != this.getNodeListSize(); i++)
			(this.getNodeAt(i)).displayInformation();
		System.out.println("");
	}

	@Override
	protected void registerNode(ConnectedNode __neuron) {
		_allNeuronsUnorderedList.add(__neuron);
	}

	/**
	 * Perform initialization of the network. Should be called only once after
	 * all neurons and arcs are registered and input and ouput neurons are
	 * identified. This method in class NeuralNetwork is provided for simple
	 * feed forward signal computation. It should be rewritten in derived
	 * classes to implement your own initialization (see for example the
	 * FeedForward- or Recurrent- NeuralNetwork classes)
	 * 
	 * The network do not handle reccurence at this point (rewrite this method
	 * and the step() method, see RecurrentNeuralNetwork class for example)
	 */
	public void initNetwork() // TODO optimise...? [!n]-20060217
	{
		/*
		 * order neurons from _allNeuronsUnorderedList with the less
		 * arc-dependent neuron first (i.e. input, hidden, output). (results
		 * will be in _allNeuronsList).
		 * 
		 */

		ArrayList temporaryList = new ArrayList(); // this list will contains
		// neurons for *one* given
		// layer

		/*
		 * test network integrity (i.e. test if all non-input neurons are
		 * directly or indirectly connected to 1+ input neuron(s)
		 */

		for (int i = 0; i != _inputNeuronsList.size(); i++)
			if (((Neuron) _inputNeuronsList.get(i)).checkNetwork(this) == false) {
				System.out.println("Network::initNetwork() - network contains either unreferenced or badly referenced neuron(s)");
				System.exit(-1);
			}

		/*
		 * move input to ordered list (and remove them from
		 * _allNeuronsUnorderedList)
		 */

		if (_inputNeuronsList.size() == 0 || _outputNeuronsList.size() == 0) {
			System.err.println("Network::initNetwork() - no input and/or output defined");
			System.exit(-1);
		}

		for (int i = _inputNeuronsList.size() - 1; i != -1; i--) // reverse
		// order (at
		// the
		// beginning
		// we know
		// that
		// sizeOf(list)>0)
		{
			_nodeList.add(_inputNeuronsList.get(i));
			_allNeuronsUnorderedList.remove(_inputNeuronsList.get(i));
		}

		/*
		 * move and order all other neurons, layers close to inputs are moved
		 * first
		 */

		if (this._allNeuronsUnorderedList.size() == 0) {
			System.err.println("Network::initNetwork() - empty list (i.e. no neuron)");
			System.exit(-1);
		} else if (this._outputNeuronsList.size() == 0) {
			System.err.println("Network::initNetwork() - no output defined");
			System.exit(-1);
		}

		while (_allNeuronsUnorderedList.size() > 0) // at the beginning, we know
		// that it is > 0 -- loop
		// until
		// _allNeuronsUnorderedList
		// is empty
		{
			int i = 0;
			boolean isCycle = true;

			while (i != _allNeuronsUnorderedList.size() && _allNeuronsUnorderedList.size() > 0) // identify
			// neurons
			// that
			// belong
			// to
			// current
			// layer
			{
				Neuron currentNeuron = (Neuron) _allNeuronsUnorderedList.get(i);
				boolean isToCopy = true;

				// test if all parents of neurons are already in _allNeuronsList
				// [yes: copy=true, no: copy=false]
				for (int j = 0; j != currentNeuron.getIncomingArcsListSize(); j++) {
					// is father k already in _allNeuronsList ?

					boolean isParentRegistered = false;

					for (int k = 0; k != _nodeList.size(); k++) {
						if (((WeightedArc) currentNeuron.getIncomingArcAt(j)).getSourceNeuron().equals(_nodeList.get(k)) == true) {
							isParentRegistered = true;
							break;
						}
					}

					if (isParentRegistered == false) {
						isToCopy = false;
						break; // this neuron cannot be handled now (i.e. will
						// be handled later)
					}
				}

				if (isToCopy == true) // do we have to handle this neuron
				// (i.e. move it to temporaryList)
				{
					temporaryList.add(_allNeuronsUnorderedList.get(i));
					_allNeuronsUnorderedList.remove(i); // on incremente pas i
					isCycle = false;
				} else
					// no? --> ignore this neuron for now
					i++;
			}

			// copy new layer to _allNeuronsList
			for (int j = 0; j != temporaryList.size(); j++)
				_nodeList.add(temporaryList.get(j));
			temporaryList.clear();

			if (isCycle == true) // no change for this pass (i.e. no new
			// dependencies resolved) --> cycle
			// detected! [error]
			{
				System.out.println("Network::initNetwork() - cannot handle network cycle");
				System.exit(-1);
			}
		}

		_allNeuronsUnorderedList.clear();

		setOldOutputRegisterSize(this._outputNeuronsList.size());
		setOutputRegisterSize(this._outputNeuronsList.size());
		setInputRegisterSize(this._inputNeuronsList.size());

		_init = true;
	}

	/**
	 * Perform one step on the whole Network.
	 */
	@Override
	public void stepModule() // should be abstracted
	{
		if (this.isUpdated() == true)
			return; // nothing to do.
		else
			this.setUpdated(true); // this node is updated

		// Execute le(s) module(s) avant pour satisfaire les dependances.
		// ( ... ) --> ne pas faire, puisque pris en charge lors de
		// getSourceOutputRegisterValue

		// save current register value as old values
		for (int i = 0; i != this.getOldOutputRegisterSize(); i++)
			this.setOldOutputRegister(i, this.getOutputRegister(i));

		if (this.getInputRegisterSize() != this.getIncomingArcsListSize())
			Display.error("NeuralNetwork::stepModule() - input sizes do not match");

		// get input from source (!n - opt note : could be optimized)
		for (int i = 0; i != this.getInputRegisterSize(); i++)
			this.setInputRegister(i, ((ModularArc) this.getIncomingArcAt(i)).getSourceOutputRegisterValue());

		// perform one running step (note that the step() method should also
		// update output registers)
		this.step();

		// copy output values into output register
		if (this.getOutputRegisterSize() != this.getOutputNeuronListSize())
			Display.error("NeuralNetwork::stepModule() - output sizes do not match");
		for (int i = 0; i != this.getOutputRegisterSize(); i++)
			this.setOutputRegister(i, this.getOutputNeuronAt(i).getValue());

		// call successors -- sub-optimal : N^2 calls in the worst case cmp to N
		// for a blackboard approach (but worst case is *RARE*)
		for (int i = 0; i != this.getOutgoingArcsListSize(); i++)
			((Module) ((ModularArc) this.getOutgoingArcAt(i)).getTarget()).stepModule();

	}

	/**
	 * compute output values from input values (i.e. f(in)=out). the current
	 * implementation assumes no recurrent connections -- this method should be
	 * rewritten when deriving this class.
	 * 
	 * @param __inputValuesList
	 */
	@Override
	public void step(ArrayList __inputValuesList) {
		if (__inputValuesList.size() != _inputNeuronsList.size()) {
			System.out.println("NeuralNetwork::step(-) - input values mismatch");
			System.exit(-1);
		}

		// copy input values into input nodes (note : if bias neuron exists, it
		// is located at the end of the input nodes list, so it is ignored)
		for (int i = 0; i != __inputValuesList.size(); i++) {
			((Neuron) _inputNeuronsList.get(i)).setInputValue(((Double) __inputValuesList.get(i)).doubleValue());
		}

		this.step();
	}

	/**
	 * compute output values from input values (i.e. f(in)=out).
	 * 
	 * @param __inputValuesList
	 */
	@Override
	public void step() {
		if (_init == false) {
			System.out.println("NeuralNetwork::step() - network not initialized!");
			System.exit(-1);
		}

		// signal feedforward-propagation and compute output neurons values.
		for (int i = _inputNeuronsList.size(); i != _nodeList.size(); i++) {
			((Neuron) _nodeList.get(i)).step();
		}

		// copy back actual output values as old output values

		for (int i = 0; i < getOutputRegisterSize(); i++)
			setOldOutputRegister(i, getOutputRegister(i));
	}

}
